/*
 * Copyright (c) gitee.com/jiandong001.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import hilog from '@ohos.hilog';

import { ViewAtIndex } from './ViewAtIndex';
import { ViewProps } from './ViewProps';
import { ReadableArray } from '../bridge/ReadableArray';
import { NativeKind } from './NativeKind';
import { Assertions } from '../inner/Assertions';
import { ReactStylesDiffMap } from './ReactStylesDiffMap';
import { ThemedReactContext } from './ThemedReactContext';
import { UIViewOperationQueue } from './UIViewOperationQueue';
import { ShadowNodeRegistry } from './ShadowNodeRegistry';
import { ReactShadowNode } from './ReactShadowNode';
/*

/**
 * Class responsible for optimizing the native view hierarchy while still respecting the final UI
 * product specified by JS. Basically, JS sends us a hierarchy of nodes that, while easy to reason
 * about in JS, are very inefficient to translate directly to native views. This class sits in
 * between {@link UIManagerModule}, which directly receives view commands from JS, and {@link
 * UIViewOperationQueue}, which enqueues actual operations on the native view hierarchy. It is able
 * to take instructions from UIManagerModule and output instructions to the native view hierarchy
 * that achieve the same displayed UI but with fewer views.
 *
 * <p>Currently this class is only used to remove layout-only views, that is to say views that only
 * affect the positions of their children but do not draw anything themselves. These views are
 * fairly common because 1) containers are used to do layouting via flexbox and 2) the return of
 * each Component#render() call in JS must be exactly one view, which means views are often wrapped
 * in a unnecessary layer of hierarchy.
 *
 * <p>This optimization is implemented by keeping track of both the unoptimized JS hierarchy and the
 * optimized native hierarchy in {@link ReactShadowNode}.
 *
 * <p>This optimization is important for view hierarchy depth (which can cause stack overflows
 * during view traversal for complex apps), memory usage, amount of time spent during GCs, and
 * time-to-display.
 *
 * <p>Some examples of the optimizations this class will do based on commands from JS: - Create a
 * view with only layout props: a description of that view is created as a {@link ReactShadowNode}
 * in UIManagerModule, but this class will not output any commands to create the view in the native
 * view hierarchy. - Update a layout-only view to have non-layout props: before issuing the
 * updateShadowNode call to the native view hierarchy, issue commands to create the view we
 * optimized away move it into the view hierarchy - Manage the children of a view: multiple
 * manageChildren calls for various parent views may be issued to the native view hierarchy
 * depending on where the views being added/removed are attached in the optimized hierarchy
 */

class NodeIndexPair {
  public node: ReactShadowNode;
  public index: number;

  constructor(node: ReactShadowNode, index: number) {
    this.node = node;
    this.index = index;
  }
}

export class NativeViewHierarchyOptimizer {

  private static readonly TAG = "NativeViewHierarchyOptimizer";
  private static readonly ENABLED: boolean = false;

  private mUIViewOperationQueue: UIViewOperationQueue;
  private mShadowNodeRegistry: ShadowNodeRegistry;
  private mTagsWithLayoutVisited: boolean[];// = new Array<boolean>(); //SparseBooleanArray

  public static assertNodeSupportedWithoutOptimizer(node: ReactShadowNode): void {
    // NativeKind.LEAF nodes require the optimizer. They are not ViewGroups so they cannot host
    // their native children themselves. Their native children need to be hoisted by the optimizer
    // to an ancestor which is a ViewGroup.
    Assertions.assertCondition(
        node.getNativeKind() != NativeKind.LEAF,
        "Nodes with NativeKind.LEAF are not supported when the optimizer is disabled");
  }

  public constructor(
      uiViewOperationQueue: UIViewOperationQueue, shadowNodeRegistry: ShadowNodeRegistry) {
      this.mUIViewOperationQueue = uiViewOperationQueue;
      this.mShadowNodeRegistry = shadowNodeRegistry;
  }

  /** Handles a createView call. May or may not actually create a native view. */
  public handleCreateView(
          node: ReactShadowNode,
          themedContext: ThemedReactContext,
          initialProps?: ReactStylesDiffMap):void {

      hilog.info(0x0000, NativeViewHierarchyOptimizer.TAG, 'handleCreateView %{public}d', 111)

      this.mUIViewOperationQueue.enqueueCreateView(null, 1, "text", null);
      return;


    if (!NativeViewHierarchyOptimizer.ENABLED) {
      // NativeViewHierarchyOptimizer.assertNodeSupportedWithoutOptimizer(node);
      let tag: number = node.getReactTag();
      this.mUIViewOperationQueue.enqueueCreateView(
          themedContext, tag, node.getViewType(), initialProps);
      return;
    }

//    boolean isLayoutOnly =
//        node.getViewClass().equals(ViewProps.VIEW_CLASS_NAME)
//            && isLayoutOnlyAndCollapsable(initialProps);
//    node.setIsLayoutOnly(isLayoutOnly);

//    if (node.getNativeKind() != NativeKind.NONE) {
      this.mUIViewOperationQueue.enqueueCreateView(
          themedContext, node.getReactTag(), node.getViewType(), initialProps);
//    }
  }

  /** Handles native children cleanup when css node is removed from hierarchy */
  public static handleRemoveNode(node: ReactShadowNode) {
      node.removeAllNativeChildren();
  }

  /**
   * Handles an updateView call. If a view transitions from being layout-only to not (or vice-versa)
   * this could result in some number of additional createView and manageChildren calls. If the view
   * is layout only, no updateView call will be dispatched to the native hierarchy.
   */
  public handleUpdateView(node: ReactShadowNode, className: string, props: ReactStylesDiffMap): void {
    if (!NativeViewHierarchyOptimizer.ENABLED) {
//      NativeViewHierarchyOptimizer.assertNodeSupportedWithoutOptimizer(node);
      this.mUIViewOperationQueue.enqueueUpdateProperties(node.getReactTag(), className, props);
      return;
    }

    let needsToLeaveLayoutOnly: boolean = node.isLayoutOnly() && !NativeViewHierarchyOptimizer.isLayoutOnlyAndCollapsable(props);
    if (needsToLeaveLayoutOnly) {
      this.transitionLayoutOnlyViewToNativeView(node, props);
    } else if (!node.isLayoutOnly()) {
      this.mUIViewOperationQueue.enqueueUpdateProperties(node.getReactTag(), className, props);
    }
  }
//
//  /**
//   * Handles a manageChildren call. This may translate into multiple manageChildren calls for
//   * multiple other views.
//   *
//   * <p>NB: the assumption for calling this method is that all corresponding ReactShadowNodes have
//   * been updated **but tagsToDelete have NOT been deleted yet**. This is because we need to use the
//   * metadata from those nodes to figure out the correct commands to dispatch. This is unlike all
//   * other calls on this class where we assume all operations on the shadow hierarchy have already
//   * completed by the time a corresponding method here is called.
//   */
  public handleManageChildren(
      nodeToManage: ReactShadowNode,
      indicesToRemove: number[],
      tagsToRemove: number[],
      viewsToAdd: ViewAtIndex[] ,
      tagsToDelete: number[]): void {
    if (!NativeViewHierarchyOptimizer.ENABLED) {
      NativeViewHierarchyOptimizer.assertNodeSupportedWithoutOptimizer(nodeToManage);
      this.mUIViewOperationQueue.enqueueManageChildren(
          nodeToManage.getReactTag(), indicesToRemove, viewsToAdd, tagsToDelete);
      return;
    }

    // We operate on tagsToRemove instead of indicesToRemove because by the time this method is
    // called, these views have already been removed from the shadow hierarchy and the indices are
    // no longer useful to operate on
    for (let i = 0; i < tagsToRemove.length; i++) {
      let tagToRemove = tagsToRemove[i];
      let removed = false;
      for (let j = 0; j < tagsToDelete.length; j++) {
        if (tagsToDelete[j] == tagToRemove) {
          removed = true;
          break;
        }
      }
      let nodeToRemove = this.mShadowNodeRegistry.getNode(tagToRemove);
      this.removeNodeFromParent(nodeToRemove, removed);
    }

    for (let i = 0; i < viewsToAdd.length; i++) {
      let toAdd = viewsToAdd[i];
      let nodeToAdd = this.mShadowNodeRegistry.getNode(toAdd.mTag);
      this.addNodeToNode(nodeToManage, nodeToAdd, toAdd.mIndex);
    }
  }
//
//  /**
//   * Handles a setChildren call. This is a simplification of handleManagerChildren that only adds
//   * children in index order of the childrenTags array
//   */
  public handleSetChildren(nodeToManage: ReactShadowNode, childrenTags: ReadableArray): void {
    if (!NativeViewHierarchyOptimizer.ENABLED) {
      NativeViewHierarchyOptimizer.assertNodeSupportedWithoutOptimizer(nodeToManage);
      this.mUIViewOperationQueue.enqueueSetChildren(nodeToManage.getReactTag(), childrenTags);
      return;
    }

    for (let i = 0; i < childrenTags.size(); i++) {
      let nodeToAdd = this.mShadowNodeRegistry.getNode(childrenTags.getInt(i));
      this.addNodeToNode(nodeToManage, nodeToAdd, i);
    }
  }

  /**
   * Handles an updateLayout call. All updateLayout calls are collected and dispatched at the end of
   * a batch because updateLayout calls to layout-only nodes can necessitate multiple updateLayout
   * calls for all its children.
   */
  public handleUpdateLayout(node: ReactShadowNode): void {
    if (!NativeViewHierarchyOptimizer.ENABLED) {
      NativeViewHierarchyOptimizer.assertNodeSupportedWithoutOptimizer(node);
      Assertions.assertNotNull(node?.getLayoutParent() != null)
      this.mUIViewOperationQueue.enqueueUpdateLayout(
          node.getLayoutParent().getReactTag(),
          node.getReactTag(),
          node.getScreenX(),
          node.getScreenY(),
          node.getScreenWidth(),
          node.getScreenHeight());
      return;
    }

    this.applyLayoutBase(node);
  }
//
  public handleForceViewToBeNonLayoutOnly(node: ReactShadowNode) {
    if (node.isLayoutOnly()) {
      this.transitionLayoutOnlyViewToNativeView(node, null);
    }
  }

  /**
   * Processes the shadow hierarchy to dispatch all necessary updateLayout calls to the native
   * hierarchy. Should be called after all updateLayout calls for a batch have been handled.
//   */
  public onBatchComplete() {
     this.mTagsWithLayoutVisited.splice(0)
  }

  private walkUpUntilNativeKindIsParent(
      node: ReactShadowNode, indexInNativeChildren: number): NodeIndexPair {
    while (node.getNativeKind() != NativeKind.PARENT) {
      let parent: ReactShadowNode = node.getParent();
      if (parent == null) {
        return null;
      }

      indexInNativeChildren =
          indexInNativeChildren
              + (node.getNativeKind() == NativeKind.LEAF ? 1 : 0)
              + parent.getNativeOffsetForChild(node);
      node = parent;
    }

    return new NodeIndexPair(node, indexInNativeChildren);
  }

  private addNodeToNode(parent: ReactShadowNode, child: ReactShadowNode, index: number) {
    let indexInNativeChildren = parent.getNativeOffsetForChild(parent.getChildAt(index));
    if (parent.getNativeKind() != NativeKind.PARENT) {
      let result = this.walkUpUntilNativeKindIsParent(parent, indexInNativeChildren);
      if (result == null) {
        // If the parent hasn't been attached to its native parent yet, don't issue commands to the
        // native hierarchy. We'll do that when the parent node actually gets attached somewhere.
        return;
      }
      parent = result.node;
      indexInNativeChildren = result.index;
    }

    if (child.getNativeKind() != NativeKind.NONE) {
      this.addNativeChild(parent, child, indexInNativeChildren);
    } else {
      this.addNonNativeChild(parent, child, indexInNativeChildren);
    }
  }

  /**
   * For handling node removal from manageChildren. In the case of removing a node which isn't
   * hosting its own children (e.g. layout-only or NativeKind.LEAF), we need to recursively remove
   * all its children from their native parents.
   */
  private removeNodeFromParent(nodeToRemove: ReactShadowNode, shouldDelete: boolean): void {
    if (nodeToRemove.getNativeKind() != NativeKind.PARENT) {
      for (let i = nodeToRemove.getChildCount() - 1; i >= 0; i--) {
        this.removeNodeFromParent(nodeToRemove.getChildAt(i), shouldDelete);
      }
    }

    let nativeNodeToRemoveFrom: ReactShadowNode = nodeToRemove.getNativeParent();
    if (nativeNodeToRemoveFrom != null) {
      let index: number = nativeNodeToRemoveFrom.indexOfNativeChild(nodeToRemove);
      nativeNodeToRemoveFrom.removeNativeChildAt(index);

      this.mUIViewOperationQueue.enqueueManageChildren(
          nativeNodeToRemoveFrom.getReactTag(),
          [index], //new Array<int>
          null,
          shouldDelete ? [nodeToRemove.getReactTag()] : null);
    }
  }

  private addNonNativeChild(
      nativeParent: ReactShadowNode, nonNativeChild: ReactShadowNode, index: number): void {
    this.addGrandchildren(nativeParent, nonNativeChild, index);
  }

  private addNativeChild(parent: ReactShadowNode, child: ReactShadowNode, index: number): void {
    parent.addNativeChildAt(child, index);
    this.mUIViewOperationQueue.enqueueManageChildren(
        parent.getReactTag(),
        null,
        [new ViewAtIndex(child.getReactTag(), index)], //new ViewAtIndex[] {}
        null);

    if (child.getNativeKind() != NativeKind.PARENT) {
      this.addGrandchildren(parent, child, index + 1);
    }
  }

  private addGrandchildren(nativeParent: ReactShadowNode, child: ReactShadowNode, index: number) {
    Assertions.assertCondition(child.getNativeKind() != NativeKind.PARENT);

    // `child` can't hold native children. Add all of `child`'s children to `parent`.
    let currentIndex = index;
    for (let i = 0; i < child.getChildCount(); i++) {
      let grandchild = child.getChildAt(i);
      Assertions.assertCondition(grandchild.getNativeParent() == null);

      // Adding this child could result in adding multiple native views
      let grandchildCountBefore = nativeParent.getNativeChildCount();
      if (grandchild.getNativeKind() == NativeKind.NONE) {
        this.addNonNativeChild(nativeParent, grandchild, currentIndex);
      } else {
        this.addNativeChild(nativeParent, grandchild, currentIndex);
      }
      let grandchildCountAfter = nativeParent.getNativeChildCount();
      currentIndex += grandchildCountAfter - grandchildCountBefore;
    }
  }

  private applyLayoutBase(node: ReactShadowNode): void {
    let tag = node.getReactTag();
    if (this.mTagsWithLayoutVisited[tag]) {
      return;
    }
    this.mTagsWithLayoutVisited[tag] = true;

    let parent = node.getParent();

    // We use screenX/screenY (which round to integer pixels) at each node in the hierarchy to
    // emulate what the layout would look like if it were actually built with native views which
    // have to have integral top/left/bottom/right values
    let x = node.getScreenX();
    let y = node.getScreenY();

    while (parent != null && parent.getNativeKind() != NativeKind.PARENT) {
      if (!parent.isVirtual()) {
        // Skip these additions for virtual nodes. This has the same effect as `getLayout*`
        // returning `0`. Virtual nodes aren't in the Yoga tree so we can't call `getLayout*` on
        // them.

        // TODO(7854667): handle and test proper clipping
        x += Math.round(parent.getLayoutX());
        y += Math.round(parent.getLayoutY());
      }

      parent = parent.getParent();
    }

    this.applyLayoutRecursive(node, x, y);
  }

  private applyLayoutRecursive(toUpdate: ReactShadowNode, x: number, y: number) {
    if (toUpdate.getNativeKind() != NativeKind.NONE && toUpdate.getNativeParent() != null) {
      let tag = toUpdate.getReactTag();
      this.mUIViewOperationQueue.enqueueUpdateLayout(
          toUpdate.getLayoutParent().getReactTag(),
          tag,
          x,
          y,
          toUpdate.getScreenWidth(),
          toUpdate.getScreenHeight());
      return;
    }

    for (let i = 0; i < toUpdate.getChildCount(); i++) {
      let child: ReactShadowNode = toUpdate.getChildAt(i);
      let childTag = child.getReactTag();
      if (this.mTagsWithLayoutVisited[childTag]) {
        continue;
      }
      this.mTagsWithLayoutVisited[childTag] = true;

      let childX = child.getScreenX();
      let childY = child.getScreenY();

      childX += x;
      childY += y;

      this.applyLayoutRecursive(child, childX, childY);
    }
  }

  private transitionLayoutOnlyViewToNativeView(
      node: ReactShadowNode, props?: ReactStylesDiffMap) {
    let parent = node.getParent();
    if (parent == null) {
      node.setIsLayoutOnly(false);
      return;
    }

    // First, remove the node from its parent. This causes the parent to update its native children
    // count. The removeNodeFromParent call will cause all the view's children to be detached from
    // their native parent.
    let childIndex = parent.indexOf(node);
    parent.removeChildAt(childIndex);
    this.removeNodeFromParent(node, false);

    node.setIsLayoutOnly(false);

    // Create the view since it doesn't exist in the native hierarchy yet
    this.mUIViewOperationQueue.enqueueCreateView(
        node.getThemedContext(), node.getReactTag(), node.getViewType(), props);

    // Add the node and all its children as if we are adding a new nodes
    parent.addChildAt(node, childIndex);
    this.addNodeToNode(parent, node, childIndex);
    for (let i = 0; i < node.getChildCount(); i++) {
      this.addNodeToNode(node, node.getChildAt(i), i);
    }

    // Update layouts since the children of the node were offset by its x/y position previously.
    // Bit of a hack: we need to update the layout of this node's children now that it's no longer
    // layout-only, but we may still receive more layout updates at the end of this batch that we
    // don't want to ignore.
    hilog.info(
        0x0000,
        NativeViewHierarchyOptimizer.TAG,
        "Transitioning LayoutOnlyView - tag: "
            + node.getReactTag()
            + " - rootTag: "
            + node.getRootTag()
            + " - hasProps: "
            + (props != null)
            + " - tagsWithLayout.size:  %{public}d",
            this.mTagsWithLayoutVisited.length);
    Assertions.assertCondition(this.mTagsWithLayoutVisited.length == 0);
    this.applyLayoutBase(node);
    for (let i = 0; i < node.getChildCount(); i++) {
      this.applyLayoutBase(node.getChildAt(i));
    }
    this.mTagsWithLayoutVisited.splice(0); //clear
  }

  private static isLayoutOnlyAndCollapsable(props: ReactStylesDiffMap): boolean {
    if (props == null) {
      return true;
    }

    if (props.hasKey(ViewProps.COLLAPSABLE) && !props.getBoolean(ViewProps.COLLAPSABLE, true)) {
      return false;
    }

    let keyIterator = props.mBackingMap.keys();
    let temp = keyIterator.next().value;
    while(temp != undefined) {
      console.log("value:" + temp);
      if (!ViewProps.isLayoutOnly(props.mBackingMap, temp as string)) {
        return false;
      }
      temp = keyIterator.next().value;
    }
    return true;
  }

  /**
   * Called when all the view updates of {@link ReactShadowNode} received as a parameter were
   * processed.
   */
  onViewUpdatesCompleted(cssNode: ReactShadowNode): void {
    // cssNode is not being used, but it is passed as a parameter in case this is required in the
    // future.
    this.mTagsWithLayoutVisited.slice(0);
  }
}
